﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Xml;
using System.Net.Sockets;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using ClassLibrary.Agent;
using ClassLibrary.Configuration;
using ClassLibrary.Configuration.Manager;
using ClassLibrary.Configuration.Switch;



namespace NetworkMenager
{
    /// <summary>
    /// @Author Dawid Leśniakiewicz
    /// Zarządca sieci
    /// </summary>
    public partial class NetworkManager : Form
    {
        // --- delegaci

        private delegate void SetTextCallback(string text);
        private delegate void AddItemCallback(string text);
        private delegate void StartConfigCallback();

        // --- port Zarządcy- odczytywany z plików konfiguracyjnych
        private int _port;
        // --- socket Zarządcy
        private Socket server;
        // --- pomocnicze przy otrzymywaniu wiadomości (z id) od agentów
        private byte[] data = new byte[1024];
        private int size = 1024;

        //[logicalPortId]
        private readonly Dictionary<String, Socket> _clientSocketsDictionary = new Dictionary<string, Socket>();

        // obiekt okna konfiguracji
        private Config config;


        /// <summary>
        /// Zarządca sieci (konstruktor)
        /// </summary>

        public NetworkManager()
        {
            config = new Config();
            ManagerConfig managerConfig;
            var args = Environment.GetCommandLineArgs();
            if (args.Length > 1)
            {
                managerConfig = (ManagerConfig)Configuration.GetConfiguration(args[1], typeof(ManagerConfig));
            }
            else
            {
                managerConfig = (ManagerConfig)Configuration.GetConfiguration(@"d://ManagerConfiguration.xml", typeof(ManagerConfig));
            }


            _port = managerConfig.Port;

            close = 0;

            InitializeComponent();
            var queryTypes = Enum.GetValues(typeof(QueryType));
            foreach (var queryType in queryTypes)
            {
                comboBox2.Items.Add(queryType);
            }
            Start();
        }



        /// <summary>
        ///  Startuje serwer Managera
        /// 
        /// </summary>



        private void Start()
        {
            server = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Any, _port);
            server.Bind(iep);
            server.Listen(4);
            server.BeginAccept(AcceptConn, server);

            SetText("Service Started.   Port: " + _port.ToString());
            groupBox2.Enabled = true;
            StartServisButton.Enabled = false;
            StartServisButton.BackColor = System.Drawing.Color.Red;
        }
        /// <summary>
        /// Wstawia tekst do listBox-a (informujący o zdarzeniu)
        /// </summary>
        /// <param name="text">tekst</param>
        private void SetText(string text)
        {

            if (this.listBox1.InvokeRequired)
            {
                SetTextCallback d = SetText;
                this.Invoke(d, new object[] { text });
            }
            else
            {
                DateTime current = DateTime.Now;
                this.listBox1.Items.Add("[ " + current.ToString() + " ]   " + text);
            }
        }
        /// <summary>
        /// W przypadku połączenia noda stawia id noda (otrzymane od agenta) do comboBox-a 1 
        /// </summary>
        /// <param name="item">id noda</param>

        private void AddItem(string item)
        {

            if (comboBox1.InvokeRequired)
            {
                AddItemCallback d = AddItem;
                Invoke(d, new object[] { item });
            }
            else
            {
                DateTime current = DateTime.Now;
                comboBox1.Items.Add(item);
            }
        }
        /// <summary>
        /// Startuje okno wyświetlające otrzymany komunikat od noda (tablicę routingową)
        /// </summary>
        private void StartConfig()
        {

            if (config.InvokeRequired)
            {
                StartConfigCallback start = StartConfig;
                Invoke(start, new object[] { });
            }
            else
            {
                DateTime current = DateTime.Now;
                config.ShowDialog();
            }
        }

        /// <summary>
        /// W przypadku rozłączenia noda usuwa id noda z comboBox-a 1
        /// </summary>
        /// <param name="item">id noda</param>

        private void RemoveItem(string item)
        {

            if (comboBox1.InvokeRequired)
            {
                AddItemCallback d = RemoveItem;
                Invoke(d, new object[] { item });
            }
            else
            {
                DateTime current = DateTime.Now;
                comboBox1.Items.Remove(item);
            }
        }

        /// <summary>
        /// Asynchronicza akceptacja podłączenia nowego agenta
        /// </summary>
        /// <param name="iar">Status operacji asynchronicznej</param>
        void AcceptConn(IAsyncResult iar)
        {
            Socket client = server.EndAccept(iar);


            client.BeginReceive(data, 0, size, SocketFlags.None,
                        OnIdReceived, client);


            server.BeginAccept(AcceptConn, server);
        }

        /// <summary>
        /// Asynchroniczne odebranie Id nowo podłączonego agenta
        /// </summary>
        /// <param name="asyn">Status operacji asynchronicznej</param>


        public void OnIdReceived(IAsyncResult asyn)
        {

            var mSocClient = (Socket)asyn.AsyncState;
            var iRx = 0;
            iRx = mSocClient.EndReceive(asyn);
            var chars = new char[iRx + 1];
            var d = System.Text.Encoding.UTF8.GetDecoder();
            d.GetChars(data, 0, iRx, chars, 0);
            var id = new String(chars).TrimEnd('\0');
            _clientSocketsDictionary[id] = mSocClient;
            mSocClient.BeginReceive(data, 0, data.Length, SocketFlags.None, ReceiveData, id);
            AddItem(id);

            SetText("Agent " + id + "  Connected." + "  LocalEndPoint: " + mSocClient.RemoteEndPoint.ToString());
        }

        /// <summary>
        /// Asynchronicze wysyłanie wiadomości konfiguracyjnej do agenta
        /// </summary>
        /// <param name="iar">Status operacji asynchronicznej</param>

        void SendData(IAsyncResult iar)
        {

            var id = (string)iar.AsyncState;
            var client = _clientSocketsDictionary[id];
            int sent = client.EndSend(iar);

            SetText("Send to agent " + id + ".  LocalEndPoint:  " + client.RemoteEndPoint.ToString());
        }

        /// <summary>
        /// Asynchroniczne odebranie wiadomości (tablicy routingowej) od agenta
        /// </summary>
        /// <param name="iar">Status operacji asynchronicznej</param>


        void ReceiveData(IAsyncResult iar)
        {
            try
            {
                var id = (String)iar.AsyncState;
                var mSocClient = _clientSocketsDictionary[id];
                try
                {
                    var iRx = mSocClient.EndReceive(iar);
                    var binaryFormatter = new BinaryFormatter();
                    var memoryStream = new MemoryStream(data, 0, iRx);
                    HandleReceived(binaryFormatter.Deserialize(memoryStream));

                    mSocClient.BeginReceive(data, 0, data.Length, SocketFlags.None, ReceiveData, id);


                }

                catch (NullReferenceException ex)
                {

                }

                catch (SocketException ex)
                {

                    SetText("Agent " + id + "  Disconnected." + "  LocalEndPoint: " +
                            mSocClient.RemoteEndPoint.ToString());
                    _clientSocketsDictionary.Remove(id);
                    RemoveItem(id);
                }
                catch (Exception ex)
                {
                }
            }
            catch (KeyNotFoundException ex)
            {

            }

        }

        /// <summary>
        /// Obsługa zdarzenia odebrania konfiguracji noda od agenta
        /// </summary>
        /// <param name="package">konfiguracja noda</param>
        private void HandleReceived(object package)
        {
            if (package is ConfSwitchingTable)
            {
                var confSwitchingTable = (ConfSwitchingTable)package;
                config.records = confSwitchingTable;
                config.addConfig();
                StartConfig();
            }
        }

        /// <summary>
        /// Obsługa przycisku "Send". Tworzy właściwy obiekt serializowany i wysyła do odpowiedniego agenta
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void Send_Click(object sender, EventArgs e)
        {

            var query = new Query()
            {
                QueryType = (QueryType)comboBox2.SelectedItem,
                Record = new ConfSwitchingTable.Record()
                {
                    Ingress = { PortId = Convert.ToInt32(numericUpDown1.Value), VcId = Convert.ToInt32(numericUpDown2.Value) },
                    Egress = { PortId = Convert.ToInt32(numericUpDown3.Value), VcId = Convert.ToInt32(numericUpDown4.Value) }
                }
            };

            if (comboBox1.SelectedItem == null)
            {
                MessageBox.Show("Choose Root where sould I send", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                String choosen = comboBox1.SelectedItem.ToString();

                try
                {
                    var binaryFormatter = new BinaryFormatter();
                    var memoryStream = new MemoryStream();
                    binaryFormatter.Serialize(memoryStream, query);
                    var serializedData = memoryStream.ToArray();
                    _clientSocketsDictionary[choosen].BeginSend(serializedData, 0, serializedData.Length, SocketFlags.None,
                        new AsyncCallback(SendData), choosen);
                }
                catch (KeyNotFoundException ex)
                {
                    MessageBox.Show("Choosen Root is not connected", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }

            }


        }

        /// <summary>
        /// Zamknięcie aplikacji
        /// </summary>
        int close;
        private void NetworkManager_FormClosing(object sender, FormClosingEventArgs e)
        {

            //  for (int i = 0; i < 1000000000; i++) { int o = 0; }
            if (close == 0)
            {
                e.Cancel = true;
                // this.Dispose();
                Closing();
            }
        }

        /// <summary>
        /// Zamknięcie połączeń z agentami
        /// </summary>
        private void Closing()
        {
            try
            {
                foreach (var clien in _clientSocketsDictionary)
                {
                    if (clien.Value != null) SetText("Agent " + clien.Key + " (RemoteEndPoint): " + clien.Value.RemoteEndPoint + "   Disconected.");
                    Thread.Sleep(1000);
                }
            }
            catch (NullReferenceException ex)
            {
            }
            finally
            {
                SetText("Service Stop.  Bye bye");

                close = 1;
                listBox1.Refresh();
                Thread.Sleep(1000);

                this.Close();
            }
        }

        /// <summary>
        /// Obsługa zmiany wyboru w comboBoxie2 (zmiana wyboru opcji konfiguracji noda). Pomaga operatorowi aplikacji.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void comboBox2_SelectionChangeCommitted(object sender, EventArgs e)
        {
            if (comboBox2.SelectedItem.Equals(QueryType.SwitchingTableRequest))
            {
                numericUpDown3.Enabled = false;
                numericUpDown4.Enabled = false;
                numericUpDown1.Enabled = false;
                numericUpDown2.Enabled = false;
            }
            else if (comboBox2.SelectedItem.Equals(QueryType.Delete))
            {
                numericUpDown1.Enabled = true;
                numericUpDown2.Enabled = true;
                numericUpDown3.Enabled = false;
                numericUpDown4.Enabled = false;
            }
            else
            {

                numericUpDown1.Enabled = true;
                numericUpDown2.Enabled = true;
                numericUpDown3.Enabled = true;
                numericUpDown4.Enabled = true;

            }



        }
    }
}

